/**
 * Intersection Observer API 交叉观察接口
 *    传统定位计算方法是依赖明确的 DOM 信息查询，这会导致样式重绘和布局的回流，一旦频繁操作，就会因为轮询带来重大的性能瓶颈。
 * 然而现在基于这样的行为已经形成了一套公共实践体系，包括但不限于：
 *    1.构建自定义的“预加载”和“延迟加载” DOM 节点和数据。
 *    2.实现数据绑定和高性能的从数据集合中加载并渲染的滚动列表（这在移动端已经是交互习惯）。
 *    3.计算元素的“可视性”。常见于通过可视性统计的广告货币化，以及调用回读的同步布局。
 * 上面的这些使用场景有一些共同的特点：
 *    1.都表现为对两个元素（两个独立元素或者一个元素与全局视窗）统计信息的被动查询。
 *    2.都不会强加硬性的隐藏要求。这意味着这些信息可以在不产生副作用的情况下被异步投递。
 *    3.都没有现有平台的原生特性支持（2019-3-18），尽管已经非常广泛地使用。
 * 另一个显著的非目标特性是关于获取实际显示部分的精确像素信息（这在现行浏览器框架下很难），但往往很有用。
 * Intersection Observer API 通过提供方法异步查询元素与元素、元素与全局视窗之间的定位信息来解决上面的问题，
 * 异步的信息传递，让我们不再需要昂贵的 DOM 查询、轮询和自定义插件，从而减少了 CPU GPU 的开销，提高了应用性能。
 * 
 * 兼容性：
 *  CanIUser：https://caniuse.com/#search=IntersectionObserver
 * 此 API 的兼容性较差（2019-03-18），IE11 及以下浏览器均不支持，ios Safari 也不支持，国内移动端两大浏览器 QQ 和 百度也不支持，所以离工业应用还有一段距离。
 * 本接口在 W3C 上处于工作草案阶段，但 Chrome 和 Firefox 在2016年已经完成了支持，可以先来看看效果
 */

/**
 * Intersection Observer API 帮助开发者理解目标元素和交叉根元素之间的可见性和定位
 * 初始化时如果给出一个交叉根元素，那么可以监听该根元素下左右目标元素，
 * 如果没有给入根元素，那就使用默认的根元素，可以是浏览器的顶级上下文，或者目标元素最近的上下文。
 * 在不需要解决额外使用场景的情况下，引入“根边距”（rootMargin）和每个目标元素的“准入值”（threshold），会带来更多的复杂性，可以通过配置对象参数，为每个观察者提供。
 * 
 * 下面是 IntersectionObserver 接口的构造函数：
 *    IntersectionObserver(IntersectionObserverCallback callback [, IntersectionObserverInit options])
 *    一参是回调函数，二参是可选的配置对象
 * 
 * IntersectionObserverInit 字典：{
 *    root: {Element}，可为空，默认为null，这个交叠观察者的根元素，如果不提供，采用隐含的根元素
 *    rootMargin: {string}，默认为'0px'，与 CSS 的 margin 属性类似，有 1-4 个部分组成，每个部分都是一个绝对长度或者百分比。
 *    threshold: {double|Array<double>}，默认为 0，触发回调函数的准入值，当交叠矩形的面积从大于等于任何准入值变为小于，或者相反时触发回调函数。准入值可选范围是[0, 1.0]的闭区间（表示交叠矩形占目标元素面积的百分比）
 * }
 * 
 * IntersectionObserver 实例字典：{
 *    root: {Element}，可为空，只读，使用交叠接口的根元素
 *    rootMargin: {string}，交叠根元素边界盒子的偏移量，有效地扩张或者收缩用于计算交的叠盒子模型。根边距属性只对属于相同浏览上下文作为交叠根元素的目标元素有效。在获取本属性时，返回值是通过空格分离的序列化字符串，通常由 1-4 个部分组成，其值与含义都跟 css 的 margin 属性类似。
 *    thresholds: {FrozenArray<double>}，只读，准入值列表，递增排序，每一个准入值就是交叠区域面积与目标元素面积的比率。当这个比率跨越给定的准入值时，将生成一个事件通知。如果没有明确给入准入值，默认为 [ 0 ]。
 *    observe: {Function(Element target)}，指定目标元素
 *    unobserve: {Function(Element target)}，顾名思义，结束对指定元素的监听。
 *    disconnect: {Function()}，结束对所有目标元素的监听
 *    takeRecords: {Function()}，清除观察者对象队列，并返回队列副本
 * }
 * 
 * 在回调函数中，我们可以获取到 IntersectionObserverEntry 类型的对象，包含了我们需要的数据信息
 * IntersectionObserverEntry 字典：{
 *    boundingClientRect: {DOMRectReadOnly}，只读，目标元素 getBoundingClientRect() 函数的返回结果
 *    intersectionRect: {DOMRectReadOnly}，只读，目标元素在跟元素上剪切的矩形，表示目标矩形与根矩形交叠的矩形
 *    isIntersection: {boolean}，只读，如果目标元素与根元素交叠则为 ture，否则为 false。这在一个过渡过程中用于判断是否交叠。
 *    intersectionRatio: {double}，只读，如果目标元素矩形面积不为0，这个值就是交叠矩形和目标元素矩形面积比值，否则如果 isIntersecting 为true 则本值为 1，为 false，本值为 0。
 *    rootBounds: {DOMRectReadOnly}，只读，可为空，如果目标元素与根元素有同源浏览上下文，则本值为根元素的边界矩形，否则本值为 null。
 *    target: {Element}，只读，当前事件中交叠状态发生改变的目标元素
 *    time: {DOMHighResTimeStamp}，只读，值是一个高精度时间戳，记录当前交叠发生的时间，相对的开始时间是当前 IntersectionObserver 实例生成的时间，
 * }
 */

/**
 * Example 1
 */
const exampleOne = () => {

  let root = document.querySelector('#container1');
  let sampleBoxGroup1 = document.querySelector('#sampleBoxGroup1');
  
  // 构建sampleBox
  const buildSampleBox = (id) => {
    let oSampleBox = document.createElement('div');
    oSampleBox.className = 'sampleBox';
    oSampleBox.id = `sampleBox${id}`;
    for(let i = 0; i < 4; i += 1) {
      let positionText = '';
      switch (i + 1) {
        case 1: positionText = 'lefttop'; break;
        case 2: positionText = 'leftbottom'; break;
        case 3: positionText = 'righttop'; break;
        case 4: positionText = 'rightbottom'; break;
      }
      let oLabel = document.createElement('div');
      oLabel.className = `label ${positionText}`;
      oLabel.innerText = '0%';
      oSampleBox.appendChild(oLabel);
    }
    return oSampleBox;
  }

  // 回调
  const ioCallback = (ioEntry) => {
    if(ioEntry.length && ioEntry.length > 0) {
      for(let i = 0; i < ioEntry.length; i += 1) {
        let temp = ioEntry[i];
        let aLabel = temp.target.getElementsByClassName('label');
        for(let j = 0; j < aLabel.length; j += 1) {
          aLabel[j].innerText = `${(temp.intersectionRatio * 100).toFixed(0)}%`;
        }
        console.log(`${temp.intersectionRatio}——${temp.target.id}`)
      }
    }
  }

  // 配置
  const options = {
    root: root,
    rootMargin: '0px',
    threshold: (() => {
      let result = [];
      for(let i = 1; i <= 100; i += 1) {
        result.push(+(i / 100).toFixed(2))
      }
      return result;
    })()
  }

  let io = new IntersectionObserver(ioCallback, options);

  for(let i = 0; i < 4; i += 1) {
    let curSampleBox = buildSampleBox(i + 1);
    sampleBoxGroup1.appendChild(curSampleBox);
    io.observe(curSampleBox);
  }

}

/**
 * Example 2
 */
const exampleTow = () => {
  let root = document.querySelector("#container2");
  let target = document.querySelector("#sampleBoxTwo");

  // 配置
  const options = {
    root: root,
    rootMargin: '0px',
    threshold: (() => {
      let result = [];
      for(let i = 5; i <= 100; i += 5) {
        result.push(+(i / 100).toFixed(2))
      }
      return result;
    })()
  }

  const ioCallback = (ioEntrys) => {
    let entry = ioEntrys[0];
    let deg = Math.round(entry.intersectionRatio * 360);
    entry.target.style.backgroundColor = `hsl(${deg}, 100%, 50%)`; // 最后居然是以 rgb 的形式给到元素上的。
    entry.target.style.color = getContraryRGB(entry.target.style.backgroundColor);
    console.log(`${entry.target.style.backgroundColor}——${deg}——${entry.target.style.color}`);
  }

  const getContraryRGB = (originRGB) => {
    let reg = /\d{1,3}/g;
    let [r, g, b] = originRGB.match(reg);
    return `rgb(${255 - r}, ${255 - g}, ${255 - b})`;
  }

  let io = new IntersectionObserver(ioCallback, options);
  io.observe(target);
}

exampleOne();
exampleTow();